// $Id: module.hpp 4 2009-09-02 11:46:48Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#ifndef XPCOM_MODULE_HPP
#define XPCOM_MODULE_HPP

#include <xpcom/config.hpp>
#include <cstddef>
#ifdef XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
#include <boost/function.hpp>
#endif // XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/inherit.hpp>
#include <boost/mpl/inherit_linearly.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/aux_/na.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_params_with_a_default.hpp>
#include <boost/type_traits/add_pointer.hpp>
#include <xpcom/aggregate.hpp>
#include <xpcom/assert.hpp>
#include <xpcom/memory.hpp>
#include <xpcom/object.hpp>
#include <xpcom/ptr.hpp>
#include <xpcom/string.hpp>
#include <xpcom/uuid.hpp>
#include <xpcom/impl/exception.hpp>

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_PREFIX
#endif

#ifndef XPCOM_LIMIT_MODULE_COMPONENT_SIZE
#  define XPCOM_LIMIT_MODULE_COMPONENT_SIZE BOOST_MPL_LIMIT_VECTOR_SIZE
#endif

#if XPCOM_LIMIT_MODULE_COMPONENT_SIZE < BOOST_MPL_LIMIT_VECTOR_SIZE
#  define XPCOM_MODULE_COMPONENT_SIZE XPCOM_LIMIT_MODULE_COMPONENT_SIZE
#else
#  define XPCOM_MODULE_COMPONENT_SIZE BOOST_MPL_LIMIT_VECTOR_SIZE
#endif

class nsIComponentManager;
class nsIFile;
class nsIModule;

namespace xpcom {

// Base class that describes a module component
struct XPCOM_DECL module_component_base
{
    enum flag_tag
    {
        flag_singleton        = 1 << 0,
        flag_threadsafe       = 1 << 1,
        flag_main_thread_only = 1 << 2,
        flag_dom_object       = 1 << 3,
        flag_plugin_object    = 1 << 4,
        flag_eager_classinfo  = 1 << 5,
        flag_content_node     = 1 << 6
    };

    enum factory_tag
    {
        void_factory,
        standard_factory,
        aggregatable_factory,
        singleton_factory
    };

    uuid            classid_;
    string          contractid_;
    string          description_;
    uint32_t        flags_;
    uint32_t        factory_;
    uuid*           iids_;
    uint32_t        iid_count_;

    module_component_base();
    virtual ~module_component_base();

    //
    // Make a new instance.
    // Need interface* for standard_factory and singleton_factory,
    // aggregatable_interface* for aggregatable_factory.
    //
    // Note: either return valid pointer or throw exception from this function.
    //       Do not return null pointer.
    //
    virtual void* new_instance() const = 0;

private:
    module_component_base(const module_component_base&);
    module_component_base& operator=(const module_component_base&);
};

// Class that describes a module component
struct XPCOM_DECL module_base
{
    BOOST_STATIC_CONSTANT(uint32_t, default_version = 0x00015000U);

    uint32_t                        version_;
    string                          name_;
    const module_component_base**   components_;
    uint32_t                        component_count_;

    explicit module_base(uint32_t count);
    virtual ~module_base();

    // This is call during module initialization.
    virtual void init(noref_ptr<nsIComponentManager> manager) = 0;

    // This is call during module shutdown.
    virtual void shutdown() = 0;

    // Destroys this object.
    // This function will be called by the library when the module is unloaded.
    virtual void destroy() = 0;

private:
    module_base(const module_base&);
    module_base& operator=(const module_base&);
};

namespace detail {

// Flags and factory type of tags
struct model_flags_masks
{
    BOOST_STATIC_CONSTANT(uint32_t, thread_model_mask =
            module_component_base::flag_threadsafe |
            module_component_base::flag_main_thread_only);

    BOOST_STATIC_CONSTANT(uint32_t, factory_model_mask =
            module_component_base::flag_singleton);
};

template <typename Tag>
struct model_flags
{
    BOOST_STATIC_CONSTANT(uint32_t, value = 0);
};

template <>
struct model_flags<thread_model::multi_threaded_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::flag_threadsafe);
};

template <>
struct model_flags<thread_model::main_threaded_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::flag_main_thread_only);
};

template <>
struct model_flags<factory_model::singleton_factory_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::flag_singleton);
};

template <typename Tag>
struct model_factory
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::void_factory);
};

template <>
struct model_factory<factory_model::standard_factory_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::standard_factory);
};

template <>
struct model_factory<factory_model::aggregatable_factory_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::aggregatable_factory);
};

template <>
struct model_factory<factory_model::singleton_factory_tag>
{
    BOOST_STATIC_CONSTANT(uint32_t,
            value = module_component_base::singleton_factory);
};

} // namespace xpcom::detail

//
// Generic module component implementation.
//
template <typename T>
class module_component : public module_component_base
{
    typedef module_component<T> this_type;
    typedef module_component_base base_type;
public:
    module_component()
#ifdef XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
        : new_instance_fun_(default_new_instance())
#endif // XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
    {
        flags_ = detail::model_flags<typename T::thread_model_tag>::value |
                detail::model_flags<typename T::factory_model_tag>::value;
        factory_ = detail::model_factory<typename T::factory_model_tag>::value;
    }

    // Attribute setters
    // classid
    this_type& classid(const uuid& classid)
    {
        classid_ = classid;
        return *this;
    }

    this_type& classid(const char* classid)
    {
        classid_.from_str(classid);
        return *this;
    }

    // contractid
    this_type& contractid(const string& contractid)
    {
        contractid_ = contractid;
        return *this;
    }

    this_type& contractid(const char* contractid)
    {
        contractid_ = contractid;
        return *this;
    }

    // description
    this_type& description(const string& description)
    {
        description_ = description;
        return *this;
    }

    this_type& description(const char* description)
    {
        description_ = description;
        return *this;
    }

    // thread_model
    template <typename Tag>
    this_type& thread_model()
    {
        flags_ &= ~detail::model_flags_masks::thread_model_mask;
        flags_ |= detail::model_flags<Tag>::value;
        return *this;
    }

    // factory_model
    template <typename Tag>
    this_type& factory_model()
    {
        flags_ &= ~detail::model_flags_masks::factory_model_mask;
        flags_ |= detail::model_flags<Tag>::value;
        factory_ = detail::model_factory<Tag>::value;
        return *this;
    }

    // Other flags
    this_type& dom_object(bool b = true)
    {
        if (b)
            flags_ |= flag_dom_object;
        else
            flags_ &= ~flag_dom_object;
        return *this;
    }

    this_type& plugin_object(bool b = true)
    {
        if (b)
            flags_ |= flag_plugin_object;
        else
            flags_ &= ~flag_plugin_object;
        return *this;
    }

    this_type& eager_classinfo(bool b = true)
    {
        if (b)
            flags_ |= flag_eager_classinfo;
        else
            flags_ &= ~flag_eager_classinfo;
        return *this;
    }

    this_type& content_node(bool b = true)
    {
        if (b)
            flags_ |= flag_content_node;
        else
            flags_ &= ~flag_content_node;
        return *this;
    }

#ifdef XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
    template <typename F>
    this_type& new_instance_fun(F f)
    {
        new_instance_fun_ = f;
        return *this;
    }
#endif // XPCOM_ENABLE_CUSTOM_NEW_INSTANCE

private:
#ifdef XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
    struct default_new_instance
    {
        T* operator()() const { return new T(); }
    };

    virtual void* new_instance() const
    {
        XPCOM_ASSERT(new_instance_fun_);
        T* p = new_instance_fun_();
        return static_cast<void*>(upcast_to_interface(p).get());
    }

    boost::function<T* ()> new_instance_fun_;

#else // XPCOM_ENABLE_CUSTOM_NEW_INSTANCE
    virtual void* new_instance() const
    {
        T* p = new T();
        return static_cast<void*>(upcast_to_interface(p).get());
    }

#endif // XPCOM_ENABLE_CUSTOM_NEW_INSTANCE

    using base_type::classid_;
    using base_type::contractid_;
    using base_type::description_;
    using base_type::flags_;
    using base_type::factory_;
};

// Entry that holds a module_component
template <typename T>
struct module_component_entry
{
    typedef module_component_entry<T> type;
    module_component<T> value;
};

//
// Generic module implementation.
//
template <
    BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(
        XPCOM_MODULE_COMPONENT_SIZE, typename T, boost::mpl::na)>
class module : public module_base
{
    typedef module_base base_type;

    typedef typename boost::mpl::vector<
        BOOST_PP_ENUM_PARAMS(XPCOM_MODULE_COMPONENT_SIZE, T)
    >::type components_type;

    typedef typename boost::mpl::inherit_linearly<
        components_type,
        boost::mpl::inherit<
            boost::mpl::_1, module_component_entry<boost::mpl::_2>
        >
    >::type component_entries_type;

public:
    module() :
        base_type(boost::mpl::size<components_type>::value)
    {
        boost::mpl::for_each<
            components_type, boost::add_pointer<boost::mpl::_1>
        >(fill_component(*this));
    }

#ifdef XPCOM_ENABLE_OBJECT_OPERATOR_NEW_DELETE
    static void* operator new(std::size_t n)
    {
        return memory::alloc_data(n);
    }

    static void operator delete(void* p, std::size_t /*n*/)
    {
        memory::free_data(p);
    }
#endif // XPCOM_ENABLE_OBJECT_OPERATOR_NEW_DELETE

    template <typename T>
    const module_component<T>& component(T* p = 0) const
    {
        return static_cast<const module_component_entry<T>&>(entries_).value;
    }

    template <typename T>
    module_component<T>& component(T* p = 0)
    {
        return static_cast<module_component_entry<T>&>(entries_).value;
    }

protected:
    virtual void init(noref_ptr<nsIComponentManager> /*manager*/)
    {
        // noop
    }

    virtual void shutdown()
    {
        // noop
    }

private:
    virtual void destroy()
    {
        // Please do finalization in destructor
        delete this;
    }

    // Functor for filling components array
    struct fill_component
    {
        fill_component(module& m) :
            m_(m), i_(0)
        {
        }

        template <typename T>
        void operator()(T* p)
        {
            XPCOM_ASSERT(i_ < m_.component_count_);
            m_.components_[i_++] = &m_.component(p);
        }

        module& m_;
        uint32_t i_;
    };

    // Component entries tuple
    component_entries_type entries_;

    using base_type::version_;
    using base_type::name_;
    using base_type::components_;
    using base_type::component_count_;
};

//
// Makes a new nsIModule instance based on the module metadata.
//
XPCOM_DECL XPCOM_METHOD new_module(
        module_base* module,
        nsIComponentManager* manager, nsIFile* location,
        nsIModule** retval) throw();

} // namespace xpcom

#undef XPCOM_MODULE_COMPONENT_SIZE

//
// Macro for implementing NSGetModule.
//
#define XPCOM_DYNAMIC_MODULE_ENTRY_POINT(name) \
    extern "C" XPCOM_EXPORT ::xpcom::result_t NSGetModule

#define XPCOM_STATIC_MODULE_ENTRY_POINT(name) \
    extern "C" ::xpcom::result_t XPCOM_LIB_CALLBACK name##_NSGetModule

#define XPCOM_MODULE_ENTRY_POINT_BODY(module_class) \
    (nsIComponentManager* manager, nsIFile* location, nsIModule** retval) \
    { \
        module_class* p = 0; \
        XPCOM_TRANSLATE_EXCEPTION_BEGIN \
        { \
            p = new module_class(); \
        } \
        XPCOM_TRANSLATE_EXCEPTION_END \
        return ::xpcom::new_module(p, manager, location, retval); \
    }

#define XPCOM_DYNAMIC_MODULE(module_class, name) \
    XPCOM_DYNAMIC_MODULE_ENTRY_POINT(name) \
    XPCOM_MODULE_ENTRY_POINT_BODY(module_class)

#define XPCOM_STATIC_MODULE(module_class, name) \
    XPCOM_STATIC_MODULE_ENTRY_POINT(name) \
    XPCOM_MODULE_ENTRY_POINT_BODY(module_class)

#ifdef XPCOM_ENABLE_STATIC_MODULE
#  define XPCOM_MODULE(module_class, name) \
    XPCOM_STATIC_MODULE(module_class, name)
#else // XPCOM_ENABLE_STATIC_MODULE
#  define XPCOM_MODULE(module_class, name) \
    XPCOM_DYNAMIC_MODULE(module_class, name)
#endif // XPCOM_ENABLE_STATIC_MODULE

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_SUFFIX
#endif

#endif // XPCOM_MODULE_HPP
